package architect.algorithn;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * SlopeOne
 *
 * @author guanxin
 * @date 2018/5/8 14:47
 */

public class SlopeOne {

    public static HashMap<Integer, Product> ratingSystem = new HashMap<>();

    public HashMap<String, Rating> martix = new HashMap<>();

    public HashSet<Integer> items = new HashSet<>();

    public void addUserRatings(Map<Integer, List<Product>> userRatings) {
        for (Map.Entry<Integer, List<Product>> userRating1 : userRatings.entrySet()) {
            //遍历所有的Item
            for (Product item1 : userRating1.getValue()) {
                //该产品的编号（具有唯一性）
                int item1Id = item1.getProductId();

                //该项目的评分
                float item1Rating = item1.getScore();

                //将产品编号字存放在hash表中
                items.add(item1Id);

                for (Map.Entry<Integer, List<Product>> userRating2 : userRatings.entrySet()) {
                    //再次遍历item，用于计算俩俩 Item 之间的差值
                    for (Product item2 : userRating2.getValue()) {
                        //过滤掉同名的项目
                        if (item2.getProductId() <= item1Id)
                            continue;

                        //该产品的名字
                        int item2Id = item2.getProductId();

                        //该项目的评分
                        float item2Rating = item2.getScore();

                        Rating ratingDiff;

                        //用表的形式构建矩阵
                        String key = Tools.getKey(item1Id, item2Id);

                        //将俩俩 Item 的差值 存放到 Rating 中
                        if (martix.keySet().contains(key))
                            ratingDiff = martix.get(key);
                        else {
                            ratingDiff = new Rating();
                            martix.put(key, ratingDiff);
                        }

                        //方便以后以后userrating的编辑操作，（add)
                        if (!ratingDiff.userIds.contains(userRating1.getKey())) {
                            //value保存差值
                            ratingDiff.setValue(ratingDiff.getValue() + item1Rating - item2Rating);

                            //说明计算过一次
                            ratingDiff.setFreq(ratingDiff.getFreq() + 1);
                        }

                        //记录操作人的ID，方便以后再次添加评分
                        ratingDiff.userIds.add(userRating1.getKey());
                    }
                }
            }
        }
    }

    public Map<Integer, Float> predict(List<Product> userRatings) {
        HashMap<Integer, Float> predictions = new HashMap<>();

        List<Integer> productIds = userRatings.stream()
                .map(product -> {
                    return product.getProductId();
                })
                .collect(Collectors.toList());

        //循环遍历_Items中所有的Items
        for (Integer itemId : this.items) {
            //过滤掉不需要计算的产品编号
            if (productIds.contains(itemId))
                continue;

            Rating itemRating = new Rating();

            // 内层遍历userRatings
            for (Product userRating : userRatings) {
                if (userRating.getProductId() == itemId)
                    continue;

                int inputItemId = userRating.getProductId();

                //获取该key对应项目的两组AVG的值
                String key = Tools.getKey(itemId, inputItemId);

                if (martix.keySet().contains(key)) {
                    Rating diff = martix.get(key);

                    //关键点：运用公式求解（这边为了节省空间，对角线两侧的值呈现奇函数的特性）
                    itemRating.setValue(itemRating.getValue()
                            + diff.getFreq()
                            * (userRating.getScore() + diff.getAverageValue() * ((itemId < inputItemId) ? 1 : -1)));

                    //关键点：运用公式求解 累计每两组的人数
                    itemRating.setFreq(itemRating.getFreq() + diff.getFreq());
                }
            }

            predictions.put(itemId, itemRating.getAverageValue());
        }

        return predictions;
    }

    private static class Tools {
        public static String getKey(int item1Id, int item2Id) {
            return (item1Id < item2Id) ? item1Id + "->" + item2Id : item2Id + "->" + item1Id;
        }
    }
}
